/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "fxVSIncludes.fx"

/*
==========================================================================================================================================

CLASSIC DYNAMIC LIGHTING

==========================================================================================================================================
*/

#ifdef PIXELSHADER
#endif



/*
==========================================================================================================================================

ACCUMULATE SURFACE DATA TO GBUFFERS

==========================================================================================================================================
*/
struct DEFERRED_PS
{
	float4 Position: SV_POSITION;
	float2 TexCoord: TEXCOORD;
	float3 Normal: NORMAL;
	float4 WorldPos : WORLDPOS;
};


// split off because it's shared with the geometry shader
DEFERRED_PS CommonDeferredVS (float4 Position, float2 TexCoord, float3 Normal)
{
	DEFERRED_PS vs_out;

	vs_out.Position = mul (EntityMatrix, Position);
	vs_out.TexCoord = TexCoord;
	vs_out.Normal = mul ((float3x3) LocalMatrix, Normal);
	vs_out.WorldPos = mul (LocalMatrix, Position);

	return vs_out;
}


#ifdef VERTEXSHADER
SURF_VS SurfDeferredVS (SURF_VS vs_in)
{
	return vs_in;
}

DEFERRED_PS MeshDeferredVS (MESH_VS vs_in)
{
	return CommonDeferredVS (
		lerp (vs_in.Position0, vs_in.Position1, lerpBlend),
		vs_in.TexCoord,
		lerp (vs_in.Normal0, vs_in.Normal1, lerpBlend)
	);
}
#endif


#ifdef GEOMETRYSHADER
[maxvertexcount(3)]
void SurfDeferredGS (triangle SURF_VS vs_in[3], inout TriangleStream<DEFERRED_PS> triStream)
{
	// duplicate Quake's fucked-up normals (QBSP didn't normalize this)
	float3 normal = cross (vs_in[0].Position.xyz - vs_in[1].Position.xyz, vs_in[2].Position.xyz - vs_in[1].Position.xyz);

	// and output our triangle
	for (int i = 0; i < 3; i++)
		triStream.Append (CommonDeferredVS (vs_in[i].Position, vs_in[i].TexCoord, normal));

	// and finish it
	triStream.RestartStrip ();
}
#endif


#ifdef PIXELSHADER
struct GB_OUTPUT
{
	float4 Colours : SV_TARGET0;
	float4 Normals : SV_TARGET1;
	float4 Position : SV_TARGET2;
};

GB_OUTPUT MeshDeferredPS (DEFERRED_PS ps_in)
{
	GB_OUTPUT ps_out;

	ps_out.Colours = surfTexture.Sample (surfSampler, ps_in.TexCoord);
	ps_out.Normals = float4 (normalize (ps_in.Normal), 0.0f);
	ps_out.Position = ps_in.WorldPos;

	return ps_out;
}

GB_OUTPUT SurfDeferredPS (DEFERRED_PS ps_in)
{
	GB_OUTPUT ps_out;

	ps_out.Colours = surfTexture.Sample (surfSampler, ps_in.TexCoord);
	ps_out.Normals = float4 (normalize (ps_in.Normal), 0.0f);
	ps_out.Position = ps_in.WorldPos;

	return ps_out;
}
#endif


/*
==========================================================================================================================================

GBUFFER DRAWING

==========================================================================================================================================
*/

struct DEFERREDLIGHT_VS
{
	float3 Colour : COLOUR;
	float3 Origin : ORIGIN;
	float3 Transformed : INVPRJ;
	float Radius : RADIUS;
};

struct DEFERREDVOLUME_VS
{
	float4 Position : POSITION;
	DEFERREDLIGHT_VS Common;
};

struct DEFERREDLIGHT_PS
{
	float4 Position : SV_POSITION;
	float3 Colour : COLOUR;
	float3 Origin : ORIGIN;
	float Radius : RADIUS;
};

DEFERREDLIGHT_PS DeferredCommonVS (float4 Position, DEFERREDLIGHT_VS vs_in)
{
	DEFERREDLIGHT_PS vs_out;

	vs_out.Position = Position;
	vs_out.Colour = vs_in.Colour;
	vs_out.Origin = vs_in.Origin;
	vs_out.Radius = vs_in.Radius;

	return vs_out;
}


#ifdef VERTEXSHADER
DEFERREDLIGHT_VS DeferredLightVS (DEFERREDLIGHT_VS vs_in)
{
	return vs_in;
}

DEFERREDLIGHT_PS DeferredVolumeVS (DEFERREDVOLUME_VS vs_in)
{
	// scale and position the light volume to the light
	// (http://en.wikipedia.org/wiki/Icosahedron - sphere radius relative to icosahedron size)
	float4x4 VolumeMatrix = float4x4 (
		float4 (vs_in.Common.Radius * 1.33f, 0, 0, vs_in.Common.Origin.x),
		float4 (0, vs_in.Common.Radius * 1.33f, 0, vs_in.Common.Origin.y),
		float4 (0, 0, vs_in.Common.Radius * 1.33f, vs_in.Common.Origin.z),
		float4 (0, 0, 0, 1)
	);

	return DeferredCommonVS (mul (WorldMatrix, mul (VolumeMatrix, vs_in.Position)), vs_in.Common);
}
#endif


#ifdef GEOMETRYSHADER
[maxvertexcount(4)]
void DeferredLightGS (point DEFERREDLIGHT_VS gs_in[1], inout TriangleStream<DEFERREDLIGHT_PS> triStream)
{
	triStream.Append (DeferredCommonVS (float4 (-1, -1, 0, 1), gs_in[0]));
	triStream.Append (DeferredCommonVS (float4 (-1,  1, 0, 1), gs_in[0]));
	triStream.Append (DeferredCommonVS (float4 ( 1, -1, 0, 1), gs_in[0]));
	triStream.Append (DeferredCommonVS (float4 ( 1,  1, 0, 1), gs_in[0]));
	triStream.RestartStrip ();
}
#endif


#ifdef PIXELSHADER
Texture2D DiffuseBuffer : register(t14);
Texture2D NormalsBuffer : register(t15);
Texture2D PositionBuffer : register(t16);
TextureCube ShadowCube : register(t17);

float4 DeferredLightPS (DEFERREDLIGHT_PS ps_in) : SV_TARGET0
{
	// read position first so we can test the light
	float3 pos = PositionBuffer.Load (int3 (ps_in.Position.xy, 0)).xyz;

	// get vector to light and evaluate distance
	float3 LightVector = ps_in.Origin - pos;
	float Dist = ps_in.Radius - length (LightVector);

	// discard if out of range
	clip (Dist);

	// read normal
	float3 norm = NormalsBuffer.Load (int3 (ps_in.Position.xy, 0)).xyz;

	// do our calculation (because we discarded out of range points we don't need to lower-bound "Add")
	float Angle = ((dot (normalize (norm), normalize (LightVector)) * 0.5f) + 0.5f) / 256.0f;
	float Add = Dist * Angle;

	// diffuse
	float3 diff = DiffuseBuffer.Load (int3 (ps_in.Position.xy, 0)).rgb;

	// shadow map
//	float ShadowSample = ShadowCube.SampleLevel (lmapSampler, float3 (-LightVector.xy, LightVector.z), 0).x;

	//float Shadow = max ((Dist * ShadowSample * TestFloat) / ps_in.Radius - DepthBias, 0.0f);
	//float Shadow = step (length (LightVector), ShadowSample);

	// run our shadow comparison
//	float3 ScaledVector = (LightVector / ps_in.Radius) * DepthBias;
//	float Shadow = ShadowSample * max (0.01f, length (LightVector)) / ps_in.Radius;
//	float len = max (0.01f, length (LightVector)) / ps_in.Radius;
//	float Shadow = (ShadowSample * exp(-(ps_in.Radius * 0.5f) * (len - DepthBias)));
	float3 ScaledVector = (LightVector / ps_in.Radius) * DepthBias;
	//float3 ScaledVector = ((ps_in.Transformed - EyePositionWorld) / ps_in.Radius) * DepthBias;
	float Depth = dot (ScaledVector, ScaledVector);
	float ShadowSample = ShadowCube.SampleLevel (lmapSampler, float3 (-1, -1, 1) * LightVector, 0).x;
	float Shadow = step (ShadowSample, Depth);

	return float4 (ps_in.Colour * Add, 0.0f);

	// and return the result
	return float4 (diff * ps_in.Colour * Add * Shadow, 0.0f);
}
#endif


/*
==========================================================================================================================================

SHADOW MAPPING

==========================================================================================================================================
*/

struct SHADOW_GS
{
	float4 Position : POSITION;
};

struct SHADOW_PS
{
	float4 Position : SV_POSITION;
	float3 LightVector : LIGHTVECTOR;
	uint RTIndex : SV_RenderTargetArrayIndex;
	uint VPIndex : SV_ViewportArrayIndex;
};

#ifdef VERTEXSHADER
SHADOW_GS SurfShadowVS (SURF_VS vs_in)
{
	SHADOW_GS vs_out;

	vs_out.Position = mul (LocalMatrix, vs_in.Position);

	return vs_out;
}


SHADOW_GS MeshShadowVS (MESH_VS vs_in)
{
	SHADOW_GS vs_out;

	vs_out.Position = mul (LocalMatrix, lerp (vs_in.Position0, vs_in.Position1, lerpBlend));

	return vs_out;
}
#endif

#ifdef GEOMETRYSHADER
[maxvertexcount(18)]
void ObjectShadowGS (triangle SHADOW_GS input[3], inout TriangleStream<SHADOW_PS> CubeMapStream)
{
	for (int f = 0; f < 6; f++)
	{
		// Compute screen coordinates
		SHADOW_PS output;

		output.RTIndex = f;
		output.VPIndex = 1;

		for (int v = 0; v < 3; v++)
		{
			output.Position = mul (ShadowMatrixArray[f], input[v].Position);
			output.LightVector = NewLightOrigin - input[v].Position.xyz;
			CubeMapStream.Append (output);
		}

		CubeMapStream.RestartStrip ();
	}
}
#endif

#ifdef PIXELSHADER
float ObjectShadowPS (SHADOW_PS ps_in) : SV_DEPTH
{
//	return max (0.01f, length (ps_in.LightVector)) / NewLightRadius;
//	float depth = max (0.01f, length (ps_in.LightVector)) / NewLightRadius;
//	return exp ((NewLightRadius * 0.5f) * depth);
	float3 ScaledVector = (ps_in.LightVector / NewLightRadius);
	return dot (ScaledVector, ScaledVector);
//	return length (ps_in.LightVector);
//	return (NewLightRadius - length (ps_in.LightVector)) / NewLightRadius;
}
/*
float4 ObjectShadowPS (SHADOW_PS ps_in) : SV_TARGET0
{
	float3 ScaledVector = (ps_in.LightVector / NewLightRadius);
	return dot (ScaledVector, ScaledVector);
}
*/
#endif
